package com.xuecheng.gateway.config;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.xuecheng.base.result.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import org.yaml.snakeyaml.Yaml;
import reactor.core.publisher.Mono;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author Mr.M
 * @version 1.0
 * @description 网关认证过虑器
 * @date 2022/9/27 12:10
 */
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {

    /**
     * 白名单
     */
    private static final List<String> whitelist;

    // 加载白名单
    static {
        // getResourceAsStream方法，获取资源是从类路径（看target文件夹下的classes文件夹）中读取，如果加了/则是从类路径的根路径开始读取，否则就是当前包读取
        try (InputStream resourceAsStream = GatewayAuthFilter.class
                .getResourceAsStream("/security-whitelist.yaml")) {
            if (resourceAsStream == null) {
                throw new RuntimeException("未找到 /security-whitelist.yml 文件");
            }
            // 使用 SnakeYAML 解析 YAML 文件
            Yaml yaml = new Yaml();
            Map<String, Object> yamlData = yaml.load(resourceAsStream);
            // 获取白名单列表
            if (yamlData != null && yamlData.containsKey("whitelist")) {
                whitelist = (List<String>) yamlData.get("whitelist");
            } else {
                whitelist = new ArrayList<>();
                log.warn("YAML 文件中未找到 whitelist 配置");
            }
        } catch (Exception e) {
            log.error("加载 /security-whitelist.yml 出错: {}", e.getMessage());
            throw new RuntimeException("加载白名单失败", e);
        }
    }

    private final TokenStore tokenStore;

    @Autowired
    public GatewayAuthFilter(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String requestUrl = exchange.getRequest().getPath().value();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        // 1. 白名单放行
        for (String url : whitelist)
            if (pathMatcher.match(url, requestUrl))
                return chain.filter(exchange);
        // 2. 检查token是否有值
        String token = getToken(exchange);
        if (StrUtil.isBlank(token))
            return buildReturnMono("jwt令牌为空，没有认证", exchange);
        // 判断是否是有效的token
        try {
            OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
            boolean expired = oAuth2AccessToken.isExpired();
            if (expired) return buildReturnMono("认证令牌已过期", exchange);
            return chain.filter(exchange);
        } catch (InvalidTokenException e) {
            log.info("认证令牌无效: {}", token);
            return buildReturnMono("认证令牌无效", exchange);
        }
    }

    /**
     * 获取token
     */
    private String getToken(ServerWebExchange exchange) {
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(tokenStr)) return null;
        String token = tokenStr.split(" ")[1];
        if (StringUtils.isBlank(token)) return null;
        return token;
    }

    /**
     * 构建返回对象
     */
    private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        String jsonString = JSON.toJSONString(new ErrorResponse(error));
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
